//
//  CommandParse.swift
//  
//
//  Created by ZhipengZheng on 2020/6/1.
//

import Foundation

public enum CommandParseError: Error {
    case errorParameter(detail: String)
    case nonParameter(detail: String)
    case childNameRepet(detail: String)
    case parameterKeyRepet(detail: String)
    case notSupport(detail: String)
    case extend(name: String, detail: String)
}

public protocol CommandProtocol: AnyObject {
    /// 命令的名字， 同时用做命令的Key
    var name: String { get }
    /// 命令描述， 在help时会使用
    var description: String { get }
    /// 所有的子命令
    var childCommands: [String: CommandProtocol] { get set }
    /// 当前命令的所有参数
    var parameters: [String: ParameterValue] { get set }
    /// 准备方法
    /// 子类在此方法内添加子命令与注册参数
    func prepare()
    /// 解析完成，子类在此方法内实现自定义操作
    func parseFinish() throws
    
    /// Help命令值
    /// 不要手动设置此属性，通过调用addHelpParameter()设置
    var helpClosure: (() -> Bool)? { get set }
    
}

public extension CommandProtocol {
    /// 注册子命令
    /// - Parameter command: 子命令
    /// - Throws: 子命令如果存在相同的key 抛出错误
    func registerChildCommand(_ command: CommandProtocol) throws {
        if let value = childCommands[command.name] {
            throw CommandParseError.childNameRepet(detail: "\(command.name) 已经存在为 \(value)")
        }
        childCommands[command.name] = command
    }
    
    /// 添加无设置值的参数
    /// - Parameters:
    ///   - key: 参数的Key前缀必须是 `-`
    ///   - miniKey: 参数的Key缩写版本
    ///   - description: 参数的详细描述
    ///   - action: 此参数对应的动作，如果返回 `true`则继续向后解析，如果返回 `false` 则直接结束解析
    /// - Throws: 参数存在相同的key 抛出错误
    func addParameter(_ key: String, miniKey: String = "", description: String, action: @escaping ParameterValue.CallBackRBoolClosure) throws {
        let parameter = try configureParameter(key, miniKey: miniKey, type: Any.self, value: action, description: description)
        parameter.onlyUseKey = true
    }
    
    /// 添加help命令对应的动作
    /// - Parameters:
    ///   - key: 参数的Key前缀必须是 `-`
    ///   - miniKey: 参数的Key缩写版本
    ///   - description: 参数的详细描述
    ///   - action: 此参数对应的动作，如果返回 `true`则继续向后解析，如果返回 `false` 则直接结束解析
    /// - Throws: 参数存在相同的key 抛出错误
    func addHelpParameter(_ key: String, miniKey: String = "", description: String, action: @escaping ParameterValue.CallBackRBoolClosure) throws {
        try addParameter(key, miniKey: miniKey, description: description, action: action)
        helpClosure = action
    }
    
    /// 添加有设置值的参数 通过变量指针修改数据
    /// - Parameters:
    ///   - key: 参数的Key前缀必须是 `-`
    ///   - miniKey: 参数的Key缩写版本
    ///   - value: 参数对应的要接收的数据对象
    ///   - description: 参数的详细描述
    /// - Throws: 参数存在相同的key 抛出错误
    func addParameter<T>(_ key: String, miniKey: String = "", value: ParameterValue.ValuePointer<T>, description: String) throws {
        try configureParameter(key, miniKey: miniKey, type: T.self, value: value, description: description)
    }
    
    /// 添加有设置值的参数 通过action传回数据 action内部应把<T>的类型显示标出，用来类型推断
    /// - Parameters:
    ///   - key: 参数的Key前缀必须是 `-`
    ///   - miniKey: 参数的Key缩写版本
    ///   - description: 参数的详细描述
    ///   - action: 当解析到此参数时，通过action回调传回解析的值
    /// - Throws: 参数存在相同的key 抛出错误
    func addParameter<T>(_ key: String, miniKey: String = "", description: String, action: @escaping ParameterValue.CallBackClosure<T>) throws {
        try addParameter(key, miniKey: miniKey, valueType: T.self, description: description, action: action)
    }
    
    /// 添加有设置值的参数 通过action传回数据 通过valueType设置<T>的类型
    /// - Parameters:
    ///   - key: 参数的Key前缀必须是 `-`
    ///   - miniKey: 参数的Key缩写版本
    ///   - valueType: 设置要解析参数的数据类型
    ///   - description: 参数的详细描述
    ///   - action: 当解析到此参数时，通过action回调传回解析的值
    /// - Throws: 参数存在相同的key 抛出错误
    func addParameter<T>(_ key: String, miniKey: String = "", valueType: T.Type, description: String, action: @escaping ParameterValue.CallBackClosure<T>) throws {
        try configureParameter(key, miniKey: miniKey, type: valueType, value: action, description: description)
    }
    
    /// 添加有设置值的参数 通过throwsAction传回数据 throwsAction内部应把<T>的类型显示标出，用来类型推断
    /// - Parameters:
    ///   - key: 参数的Key前缀必须是 `-`
    ///   - miniKey: 参数的Key缩写版本
    ///   - description: 参数的详细描述
    ///   - throwsAction: 当解析到此参数时，通过throwsAction回调传回解析的值 , throwsAction 内部可以抛出错误 最终将错误传到CommandParse.parse()
    /// - Throws: 参数存在相同的key 抛出错误
    func addParameter<T>(_ key: String, miniKey: String = "", description: String, throwsAction: @escaping ParameterValue.CallBackThrowsClosure<T>) throws {
        try addParameter(key, miniKey: miniKey, valueType: T.self, description: description, throwsAction: throwsAction)
    }
    
    /// 添加有设置值的参数，通过throwsAction传回数据，throwsAction内部可以抛出错误， 通过valueType设置<T>的类型
    /// - Parameters:
    ///   - key: 参数的Key前缀必须是 `-`
    ///   - miniKey: 参数的Key缩写版本
    ///   - valueType: 设置要解析参数的数据类型
    ///   - description: 参数的详细描述
    ///   - throwsAction: 当解析到此参数时，通过throwsAction回调传回解析的值 , throwsAction 内部可以抛出错误 最终将错误传到CommandParse.parse()
    /// - Throws: 参数存在相同的key 抛出错误
    func addParameter<T>(_ key: String, miniKey: String = "", valueType: T.Type, description: String, throwsAction: @escaping ParameterValue.CallBackThrowsClosure<T>) throws {
        try configureParameter(key, miniKey: miniKey, type: valueType, value: throwsAction, description: description)
    }
    
    @discardableResult
    private func configureParameter(_ key: String, miniKey: String, type: Any, value: Any, description: String) throws -> ParameterValue {
        assert(key.hasPrefix("-"), "参数key的前缀必须是 `-`")
        if let parameter = parameters[key] {
            throw CommandParseError.parameterKeyRepet(detail: "\(key) 已经存在 \(parameter.value)")
        }
        let parameter = ParameterValue(key: key, miniKey: miniKey, type: type, value: value, description: description)
        parameters[key] = parameter
        if (!miniKey.isEmpty) {
            assert(miniKey.hasPrefix("-"), "参数miniKey的前缀必须是 `-`")
            parameters[miniKey] = parameter
        }
        return parameter
    }
    
    /// 解析命令行参数
    /// - Parameter reversedArguments: 传入的数据必须是正常命令数组的倒序
    /// - Throws: 解析某个命令或参数失败时会抛出错误
    func parseReversedArguments(_ reversedArguments: [String]) throws {
        guard let firstArg = reversedArguments.last else { return }
        
        var tmpArguments: [String] = reversedArguments
        if let child = childCommands[firstArg] {
            tmpArguments.removeLast()
            if tmpArguments.isEmpty {
                try child.parseFinish()
            } else {
                try child.parseReversedArguments(tmpArguments)
            }
            return
        }
 
        var flag = true
        var key = firstArg
        repeat {
            if let value = parameters[key] {
                tmpArguments.removeLast()
                tmpArguments = try value.parseValue(withReversed: tmpArguments)
                guard let tmpKey = tmpArguments.last else { break }
                key = tmpKey
            } else {
                flag = false
            }
        } while flag
        
        if (tmpArguments.isEmpty) {
            try parseFinish()
        } else {
            let argus: [String] = tmpArguments.reversed()
            throw CommandParseError.extend(name: "解析失败", detail: "未能识别的参数为 \(argus)")
        }
    }
    
    func allParameterDescription() -> String {
        var description = ""
        if parameters.count > 0 {
             let paramSet = Set<ParameterValue>(parameters.values)
             let paramEnumerated = paramSet.enumerated()
             let values = paramEnumerated.sorted(by: { $0.element.key < $1.element.key })
             description += """
             参数列表:
             \(values.reduce("", { $0 + "\n" + $1.element.key + " " + $1.element.miniKey + ": \n" + $1.element.description }))
             """
         }
        return description
    }
    
    func allChildCommandDescription() -> String {
        var description = ""
        if childCommands.count > 0 {
            description += """
            功能列表:
            \(childCommands.values.sorted(by: { $0.name < $1.name }).reduce("", { $0 + "\n" + $1.description }))
            """
        }
        return description
    }
    
}

/// 当前命令的根命令
public var CommandName: String {
    return commandName
}

var commandName: String = ""

final public class CommandParse: CommandProtocol {
    
    public convenience init(description: String) {
        let path = CommandLine.arguments.first ?? ""
        let name = URL(fileURLWithPath: path).lastPathComponent
        self.init(name: name, description: description)
    }
        
    public init(name: String, description: String) {
        self.name = name
        self.description = description
        commandName = name
    }
    
    /// 开始处理终端参数
    /// 在调用此方法前，先将需要添加的参数`addParameter()`与子命令`registerChildCommand()`配置好
    public func parse() throws {
        prepare()
        var arguments: [String] = CommandLine.arguments.reversed()
        guard let _ = arguments.popLast() else { return }
        if arguments.isEmpty {
            try parseFinish()
        } else {
            try parseReversedArguments(arguments)
        }
    }
    
    public func addDefaultHelpParameter() {
        let help = """
            Show All Help Message
        """
        try! addHelpParameter("--help", miniKey: "-h", description: help, action: { [weak self] () -> Bool in
            guard let self = self else { return true }
            self.handleDefaultHelpAction()
            return false
        })
    }
    
    public func handleDefaultHelpAction() {
        let help = """
        使用方法 \(name) [function ...] ([key] [value] ...)
        [] 内为必填项， ()内选填项
        
        \(description)
        
        \(allParameterDescription())
        
        \(allChildCommandDescription())
        """
        print(help)
    }
    
    //MARK: CommandProtocol
    public var name: String
    
    public var description: String
    
    public var parameters: [String: ParameterValue] = [:]
    
    public var childCommands: [String: CommandProtocol] = [:]
    
    public var helpClosure: (() -> Bool)?
    
    public func prepare() {
        for child in childCommands.values {
            child.prepare()
        }
    }
    
    public func parseFinish() throws {
        if let closure = helpClosure {
            _ = closure()
        } else {
            throw CommandParseError.extend(name: "缺少Help描述", detail: "使用 addHelpParameter(_:miniKey:description:action:) 添加Help描述")
        }
    }

}
